// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------

namespace System.Fabric.Common.Tracing
{
    using System;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;
    using System.Diagnostics.Tracing;
    using System.Fabric.Strings;
    using System.Globalization;
    using System.Linq;
    using System.Reflection;
    using System.Text;

    internal class TraceEvent
    {
        public const ushort MaxFieldsPerEvent = 32;
        public const ulong AdminChannelKeywordMask = 0x8000000000000000;
        public const ulong DebugChannelKeywordMask = 0x1000000000000000;
        public const ulong OperationalChannelKeywordMask = 0x4000000000000000;
 
        // Every major release, this is incremented by 10. For CU's, it's incremented by 1. Keep this value in sync with TraceEvent.cpp.
        private const byte version = 2;
        private const string FabricTracesTestKeywordEnvironmentVariable = "FabricTracesTestKeyword";

        private static readonly ulong TestKeyword = InitializeTestKeyword();

        private static int samplingCount;

        private readonly IVariantEventWriter variantEventWriter;
        private readonly EventTask taskId;
        private readonly string taskName;
        private readonly string eventName;
        private readonly EventLevel level;
        private readonly string format;
        private readonly bool[] filterStates;
        internal readonly bool hasId;
        internal readonly int typeFieldIndex;
        private GenericEventDescriptor descriptor;
        private int samplingRatio;
        private bool isProvisionalFeatureEnabled;
        private bool isProvisionalEvent;
        private AgeBasedCache<VariantId, Variant[]> provisionalCache;
        private ProvisionalMetadataAttribute provisionalData;
        private int[] idPositions;
        private int[] toExcludePosition;

        public readonly EventExtendedMetadataAttribute ExtendedMetadata;

#if DotNetCoreClrLinux
       // TODO - Following code will be removed once fully transitioned to structured traces in Linux
        private bool linuxStructuredTracesEnabled;
#endif

        public TraceEvent(
            IVariantEventWriter variantEventWriter,
            EventTask taskId,
            ushort eventId,
            string eventName,
            EventLevel level,
            EventOpcode opcode,
            EventChannel channel,
            EventKeywords keywords,
            string format,
            bool hasId,
            ProvisionalMetadataAttribute provisionalAttribute,
            int typeFieldIndex,
            EventExtendedMetadataAttribute extendedMetadataAttribute = null)
        {
            if (string.IsNullOrEmpty(eventName))
            {
                throw new ArgumentException(
                    StringResources.Error_EventNameNullOrEmpty,
                    "eventName");
            }

            // We need to apply a keyword mask to the keyword in order for it to show up
            // in the Windows Event Log. The mask that is applied depends on the channel
            // in which we want the event to show up.
            //
            // TODO: We are currently hard-coding the mask values, but this is not ideal 
            // because we have reverse-engineered these values by looking at the header 
            // file generated by mc.exe. The algorithm to determine these values is an 
            // internal implementation detail of mc.exe. A better alternative would be
            // to get the mask value from the source file generated by mc.exe, instead
            // of hard-coding it as a constant here.
            ulong keywordMask = 0;
            switch (channel)
            {
                case EventChannel.Admin:
                    keywordMask = AdminChannelKeywordMask;
                    break;
                case EventChannel.Debug:
                    keywordMask = DebugChannelKeywordMask;
                    break;
                case EventChannel.Operational:
                    keywordMask = OperationalChannelKeywordMask;
                    break;
            }

            long keywordWithMask;
            unchecked
            {
                keywordWithMask = (long)((ulong)keywords | keywordMask | TestKeyword);
            }

            // We ignore the opcode field for now. Manifest generation does the same.
            this.descriptor = new GenericEventDescriptor(
                eventId,
                version,
                (byte)channel,
                (byte)level,
                0,
                (int)taskId,
                keywordWithMask);

            this.eventName = eventName;
            this.taskId = taskId;
            this.level = level;
            this.filterStates = new bool[(int)TraceSinkType.Max];
            this.samplingRatio = 0;
            this.hasId = hasId;
            this.typeFieldIndex = typeFieldIndex;
            this.format = format;

            this.taskName =
                typeof(FabricEvents.Tasks).GetFields().Single(f => (EventTask)f.GetRawConstantValue() == taskId).Name;

            this.variantEventWriter = variantEventWriter;

            // By default, provisional feature is disabled during object initialization. This needs to be enabled
            // by calling the Update function.
            this.isProvisionalFeatureEnabled = false;

            this.provisionalData = provisionalAttribute;
            if (this.provisionalData != null)
            {
                this.isProvisionalEvent = true;
                this.provisionalCache = AgeBasedCache<VariantId, Variant[]>.DefaultInstance;
                this.PopulatePositions();
            }

            for (TraceSinkType sink = 0; sink < TraceSinkType.Max; sink++)
            {
                var status = TraceConfig.GetEventEnabledStatus(sink, this.level, this.taskId, this.eventName);
                var sampling = TraceConfig.GetEventSamplingRatio(sink, this.level, this.taskId, this.eventName);

                this.UpdateProvisionalFeatureStatus(sink, TraceConfig.GetEventProvisionalFeatureStatus(sink));
                this.UpdateSinkEnabledStatus(sink, status);
                this.UpdateSinkSamplingRatio(sink, sampling);
            }

#if DotNetCoreClrLinux
            // TODO - Following code will be removed once fully transitioned to structured traces in Linux
            this.linuxStructuredTracesEnabled = TraceConfig.GetLinuxStructuredTracesEnabled();
            TraceConfig.OnLinuxStructuredTracesEnabledUpdate += this.UpdateLinuxStructuredTracesEnabled;
#endif

            this.ExtendedMetadata = extendedMetadataAttribute;
        }

        /// <summary>
        /// // Test ETW keywords. Values must be powers of 2, starting with 0x1.
        /// </summary>
        private enum TestKeywords : long
        {
            // These 4 bits are reserved for tests and is for differentiating between rings when tests are running in parallel
            // without this all events will go to all sessions. With this we can setup keyword masks to only have events for 
            // a specific test going to its sessions.
            Test0 = 0x0000100000000000,
            Test1 = 0x0000200000000000,
            Test2 = 0x0000400000000000,
            Test3 = 0x0000800000000000
        }

        public string Message
        {
            get { return this.format; }
        }

        public string EventName
        {
            [SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Used in some dlls while not in others")]
            get
            {
                return this.eventName;
            }
        }

        public EventLevel Level
        {
            get { return this.level; }
        }

        public EventTask TaskId
        {
            get { return this.taskId; }
        }

        private bool IsProvisionalEnabled
        {
            get { return this.isProvisionalFeatureEnabled && this.isProvisionalEvent; }
        }

        public void UpdateSinkEnabledStatus(TraceSinkType sinkType, bool enabled)
        {
            this.filterStates[(int)sinkType] = enabled;
        }

        public void UpdateSinkSamplingRatio(TraceSinkType sinkType, int newSamplingRatio)
        {
            if (sinkType == TraceSinkType.ETW)
            {
                this.samplingRatio = newSamplingRatio;
            }
        }

        /// <summary>
        /// Update the Status of the Provisional Feature
        /// </summary>
        /// <param name="sinkType">Type of the sink</param>
        /// <param name="isEnabled">If the feature is enabled or not</param>
        public void UpdateProvisionalFeatureStatus(TraceSinkType sinkType, bool isEnabled)
        {
            if (sinkType == TraceSinkType.ETW)
            {
                this.isProvisionalFeatureEnabled = isEnabled;
            }
        }

        private static ulong InitializeTestKeyword()
        {
            unchecked
            {
                string testKeywordString = Environment.GetEnvironmentVariable(FabricTracesTestKeywordEnvironmentVariable);
                if (string.IsNullOrEmpty(testKeywordString))
                {
                    return 0;
                }

                TestKeywords testKeywordEnum;
                if (!Enum.TryParse(testKeywordString, out testKeywordEnum))
                {
                    Debug.Fail(string.Format(
                            "Environment variable {0} could not be parsed as a trace keyword.",
                            FabricTracesTestKeywordEnvironmentVariable));
                    return 0;
                }

                return (ulong)testKeywordEnum;
            }
        }

        public bool AreFlatSinksEnabled()
        {
            return this.IsFileSinkEnabled() || this.IsConsoleSinkEnabled();
        }

        public void WriteToFlatEventSinks(params Variant[] variants)
        {
            var args = variants.Select(v => v.ToObject()).ToArray();
            StringBuilder output = new StringBuilder(512);
            output.AppendFormat(CultureInfo.InvariantCulture, this.format, args);
            string text = output.ToString();

            if (this.IsConsoleSinkEnabled())
            {
                TraceConsoleSink.Write(this.level, text);
            }

            if (IsFileSinkEnabled())
            {
                TraceTextFileSink.Write(
                    this.taskName,
                    this.eventName.EndsWith("Text") ? args[1].ToString() : this.eventName,
                    this.hasId && args.Length > 0 ? args[0].ToString() : null,
                    this.level,
                    text);
            }
        }

        public bool IsEtwSinkEnabled()
        {
#if !DotNetCoreClrLinux
            return (this.filterStates[(int)TraceSinkType.ETW] ||
                    ((this.samplingRatio > 0) && (++TraceEvent.samplingCount % this.samplingRatio == 0)))
                   && this.variantEventWriter.IsEnabled(this.descriptor.Level, this.descriptor.Keywords);
#else
            // variantEventWriter.IsEnabled set to true
            return (this.filterStates[(int)TraceSinkType.ETW] ||
                    ((this.samplingRatio > 0) && (++TraceEvent.samplingCount % this.samplingRatio == 0)));
#endif
        }

#if DotNetCoreClrLinux
        // TODO - Following code will be removed once fully transitioned to structured traces in Linux
        internal void UpdateLinuxStructuredTracesEnabled(bool enabled)
        {
            this.linuxStructuredTracesEnabled = enabled;
        }

        internal bool IsLinuxStructuredTracesEnabled()
        {
            return this.linuxStructuredTracesEnabled;
        }
#endif

        public void WriteToEtwSink()
        {
            if (this.IsProvisionalEnabled)
            {
                this.WriteProvisional(new Variant[0]);
                return;
            }

#if DotNetCoreClrLinux
            // TODO - Following code will be removed once fully transitioned to structured traces in Linux
            if (!this.linuxStructuredTracesEnabled)
            {
                this.variantEventWriter.VariantWriteLinuxUnstructured(ref this.descriptor, 0);
                return;
            }
#endif
            this.variantEventWriter.VariantWrite(ref this.descriptor, 0);
        }

        public void WriteToEtwSink(Variant param0)
        {
            if (this.IsProvisionalEnabled)
            {
                this.WriteProvisional(new[] { param0 });
                return;
            }

#if DotNetCoreClrLinux
            // TODO - Following code will be removed once fully transitioned to structured traces in Linux
            if (!this.linuxStructuredTracesEnabled)
            {
                this.variantEventWriter.VariantWriteLinuxUnstructured(ref this.descriptor, 1, param0);
                return;
            }
#endif
            this.variantEventWriter.VariantWrite(ref this.descriptor, 1, param0);
        }

        public void WriteToEtwSink(Variant param0, Variant param1)
        {
            if (this.IsProvisionalEnabled)
            {
                this.WriteProvisional(new[] { param0, param1 });
                return;
            }

#if DotNetCoreClrLinux
            // TODO - Following code will be removed once fully transitioned to structured traces in Linux
            if (!this.linuxStructuredTracesEnabled)
            {
                this.variantEventWriter.VariantWriteLinuxUnstructured(ref this.descriptor, 2, param0, param1);
                return;
            }
#endif
            this.variantEventWriter.VariantWrite(ref this.descriptor, 2, param0, param1);
        }

        public void WriteToEtwSink(Variant param0, Variant param1, Variant param2)
        {
            if (this.IsProvisionalEnabled)
            {
                this.WriteProvisional(new[] { param0, param1, param2 });
                return;
            }

#if DotNetCoreClrLinux
            // TODO - Following code will be removed once fully transitioned to structured traces in Linux
            if (!this.linuxStructuredTracesEnabled)
            {
                this.variantEventWriter.VariantWriteLinuxUnstructured(ref this.descriptor, 3, param0, param1, param2);
                return;
            }
#endif
            this.variantEventWriter.VariantWrite(ref this.descriptor, 3, param0, param1, param2);
        }

        public void WriteToEtwSink(Variant param0, Variant param1, Variant param2, Variant param3)
        {
            if (this.IsProvisionalEnabled)
            {
                this.WriteProvisional(new[] { param0, param1, param2, param3 });
                return;
            }

#if DotNetCoreClrLinux
            // TODO - Following code will be removed once fully transitioned to structured traces in Linux
            if (!this.linuxStructuredTracesEnabled)
            {
                this.variantEventWriter.VariantWriteLinuxUnstructured(ref this.descriptor, 4, param0, param1, param2, param3);
                return;
            }
#endif
            this.variantEventWriter.VariantWrite(ref this.descriptor, 4, param0, param1, param2, param3);
        }

        public void WriteToEtwSink(Variant param0, Variant param1, Variant param2, Variant param3, Variant param4)
        {
            if (this.IsProvisionalEnabled)
            {
                this.WriteProvisional(new[] { param0, param1, param2, param3, param4 });
                return;
            }

#if DotNetCoreClrLinux
            // TODO - Following code will be removed once fully transitioned to structured traces in Linux
            if (!this.linuxStructuredTracesEnabled)
            {
                this.variantEventWriter.VariantWriteLinuxUnstructured(ref this.descriptor, 5, param0, param1, param2, param3, param4);
                return;
            }
#endif
            this.variantEventWriter.VariantWrite(ref this.descriptor, 5, param0, param1, param2, param3, param4);
        }

        public void WriteToEtwSink(Variant param0, Variant param1, Variant param2, Variant param3, Variant param4, Variant param5)
        {
            if (this.IsProvisionalEnabled)
            {
                this.WriteProvisional(new[] { param0, param1, param2, param3, param4, param5 });
                return;
            }

#if DotNetCoreClrLinux
            // TODO - Following code will be removed once fully transitioned to structured traces in Linux
            if (!this.linuxStructuredTracesEnabled)
            {
                this.variantEventWriter.VariantWriteLinuxUnstructured(ref this.descriptor, 6, param0, param1, param2, param3, param4, param5);
                return;
            }
#endif
            this.variantEventWriter.VariantWrite(ref this.descriptor, 6, param0, param1, param2, param3, param4, param5);
        }

        public void WriteToEtwSink(Variant param0, Variant param1, Variant param2, Variant param3, Variant param4, Variant param5, Variant param6)
        {
            if (this.IsProvisionalEnabled)
            {
                this.WriteProvisional(new[] { param0, param1, param2, param3, param4, param5, param6 });
                return;
            }

#if DotNetCoreClrLinux
            // TODO - Following code will be removed once fully transitioned to structured traces in Linux
            if (!this.linuxStructuredTracesEnabled)
            {
                this.variantEventWriter.VariantWriteLinuxUnstructured(ref this.descriptor, 7, param0, param1, param2, param3, param4, param5, param6);
                return;
            }
#endif
            this.variantEventWriter.VariantWrite(ref this.descriptor, 7, param0, param1, param2, param3, param4, param5, param6);
        }

        public void WriteToEtwSink(Variant param0, Variant param1, Variant param2, Variant param3, Variant param4, Variant param5, Variant param6, Variant param7)
        {
            if (this.IsProvisionalEnabled)
            {
                this.WriteProvisional(new[] { param0, param1, param2, param3, param4, param5, param6, param7 });
                return;
            }

#if DotNetCoreClrLinux
            // TODO - Following code will be removed once fully transitioned to structured traces in Linux
            if (!this.linuxStructuredTracesEnabled)
            {
                this.variantEventWriter.VariantWriteLinuxUnstructured(ref this.descriptor, 8, param0, param1, param2, param3, param4, param5, param6, param7);
                return;
            }
#endif
            this.variantEventWriter.VariantWrite(ref this.descriptor, 8, param0, param1, param2, param3, param4, param5, param6, param7);
        }

        public void WriteToEtwSink(Variant param0, Variant param1, Variant param2, Variant param3, Variant param4, Variant param5, Variant param6, Variant param7, Variant param8)
        {
            if (this.IsProvisionalEnabled)
            {
                this.WriteProvisional(new[] { param0, param1, param2, param3, param4, param5, param6, param7, param8 });
                return;
            }

#if DotNetCoreClrLinux
            // TODO - Following code will be removed once fully transitioned to structured traces in Linux
            if (!this.linuxStructuredTracesEnabled)
            {
                this.variantEventWriter.VariantWriteLinuxUnstructured(ref this.descriptor, 9, param0, param1, param2, param3, param4, param5, param6, param7, param8);
                return;
            }
#endif
            this.variantEventWriter.VariantWrite(ref this.descriptor, 9, param0, param1, param2, param3, param4, param5, param6, param7, param8);
        }

        public void WriteToEtwSink(Variant param0, Variant param1, Variant param2, Variant param3, Variant param4, Variant param5, Variant param6, Variant param7, Variant param8, Variant param9)
        {
            if (this.IsProvisionalEnabled)
            {
                this.WriteProvisional(new[] { param0, param1, param2, param3, param4, param5, param6, param7, param8, param9 });
                return;
            }

#if DotNetCoreClrLinux
            // TODO - Following code will be removed once fully transitioned to structured traces in Linux
            if (!this.linuxStructuredTracesEnabled)
            {
                this.variantEventWriter.VariantWriteLinuxUnstructured(ref this.descriptor, 10, param0, param1, param2, param3, param4, param5, param6, param7, param8, param9);
                return;
            }
#endif
            this.variantEventWriter.VariantWrite(ref this.descriptor, 10, param0, param1, param2, param3, param4, param5, param6, param7, param8, param9);
        }

        public void WriteToEtwSink(Variant param0, Variant param1, Variant param2, Variant param3, Variant param4, Variant param5, Variant param6, Variant param7, Variant param8, Variant param9, Variant param10)
        {
            if (this.IsProvisionalEnabled)
            {
                this.WriteProvisional(new[] { param0, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10 });
                return;
            }

#if DotNetCoreClrLinux
            // TODO - Following code will be removed once fully transitioned to structured traces in Linux
            if (!this.linuxStructuredTracesEnabled)
            {
                this.variantEventWriter.VariantWriteLinuxUnstructured(ref this.descriptor, 11, param0, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10);
                return;
            }
#endif
            this.variantEventWriter.VariantWrite(ref this.descriptor, 11, param0, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10);
        }

        public void WriteToEtwSink(Variant param0, Variant param1, Variant param2, Variant param3, Variant param4, Variant param5, Variant param6, Variant param7, Variant param8, Variant param9, Variant param10, Variant param11)
        {
            if (this.IsProvisionalEnabled)
            {
                this.WriteProvisional(new[] { param0, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11 });
                return;
            }

#if DotNetCoreClrLinux
            // TODO - Following code will be removed once fully transitioned to structured traces in Linux
            if (!this.linuxStructuredTracesEnabled)
            {
                this.variantEventWriter.VariantWriteLinuxUnstructured(ref this.descriptor, 12, param0, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11);
                return;
            }
#endif
            this.variantEventWriter.VariantWrite(ref this.descriptor, 12, param0, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11);
        }

        public void WriteToEtwSink(Variant param0, Variant param1, Variant param2, Variant param3, Variant param4, Variant param5, Variant param6, Variant param7, Variant param8, Variant param9, Variant param10, Variant param11, Variant param12)
        {
            if (this.IsProvisionalEnabled)
            {
                this.WriteProvisional(new[] { param0, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12 });
                return;
            }

#if DotNetCoreClrLinux
            // TODO - Following code will be removed once fully transitioned to structured traces in Linux
            if (!this.linuxStructuredTracesEnabled)
            {
                this.variantEventWriter.VariantWriteLinuxUnstructured(ref this.descriptor, 13, param0, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12);
                return;
            }
#endif
            this.variantEventWriter.VariantWrite(ref this.descriptor, 13, param0, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12);
        }

        public void WriteToEtwSink(Variant param0, Variant param1, Variant param2, Variant param3, Variant param4, Variant param5, Variant param6, Variant param7, Variant param8, Variant param9, Variant param10, Variant param11, Variant param12, Variant param13)
        {
            if (this.IsProvisionalEnabled)
            {
                this.WriteProvisional(new[] { param0, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13 });
                return;
            }

#if DotNetCoreClrLinux
            // TODO - Following code will be removed once fully transitioned to structured traces in Linux
            if (!this.linuxStructuredTracesEnabled)
            {
                this.variantEventWriter.VariantWriteLinuxUnstructured(ref this.descriptor, 14, param0, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13);
                return;
            }
#endif
            this.variantEventWriter.VariantWrite(ref this.descriptor, 14, param0, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13);
        }

        private void PopulatePositions()
        {
            this.idPositions = GetSetPositions(this.provisionalData.PositionOfIdElements);
            this.toExcludePosition = GetSetPositions(this.provisionalData.PositionToExcludeFromTrace);
        }

        private static int[] GetSetPositions(Position positions)
        {
            var count = GetSetBitCount((int)positions);
            var setPositions = new int[count];

            int index = 0;
            if (positions.HasFlag(Position.Zero))
            {
                setPositions[index++] = 0;
            }

            if (positions.HasFlag(Position.One))
            {
                setPositions[index++] = 1;
            }

            if (positions.HasFlag(Position.Two))
            {
                setPositions[index++] = 2;
            }

            if (positions.HasFlag(Position.Three))
            {
                setPositions[index++] = 3;
            }

            if (positions.HasFlag(Position.Four))
            {
                setPositions[index++] = 4;
            }

            if (positions.HasFlag(Position.Five))
            {
                setPositions[index++] = 5;
            }

            if (positions.HasFlag(Position.Six))
            {
                setPositions[index++] = 6;
            }

            if (positions.HasFlag(Position.Seven))
            {
                setPositions[index++] = 7;
            }

            if (positions.HasFlag(Position.Eight))
            {
                setPositions[index++] = 8;
            }

            if (positions.HasFlag(Position.Nine))
            {
                setPositions[index++] = 9;
            }

            if (positions.HasFlag(Position.Ten))
            {
                setPositions[index++] = 10;
            }

            if (positions.HasFlag(Position.Eleven))
            {
                setPositions[index++] = 11;
            }

            if (positions.HasFlag(Position.Twelve))
            {
                setPositions[index++] = 12;
            }

            if (positions.HasFlag(Position.Thirteen))
            {
                setPositions[index] = 13;
            }

            return setPositions;
        }

        private void WriteProvisional(Variant[] variants)
        {
            VariantId uniqueId = new VariantId(this.idPositions.Length);
            foreach (int one in this.idPositions)
            {
                uniqueId.AddIdElement(variants[one]);
            }

            // If this is the end of the Chain, remove any earlier links [if present] and flush this event.
            if (this.provisionalData.FlushAndClose)
            {
                this.provisionalCache.RemoveIfPresent(uniqueId);
                this.FlushProvisional(variants);
                return;
            }

            Variant[] etwValues = this.toExcludePosition.Length != 0 ? ExceptPositions(variants, this.toExcludePosition) : variants;

            // Push this link of the chain into the provisional cache. If add to cache fails, flush the event.
            if (!this.provisionalCache.TryAddOrUpdate(
                uniqueId,
                etwValues,
                TimeSpan.FromMilliseconds(this.provisionalData.ProvisionalTimeInMs),
                this.FlushProvisional))
            {
                this.FlushProvisional(etwValues);
            }
        }

        private void FlushProvisional(Variant[] variants)
        {
#if DotNetCoreClrLinux
            // TODO - Following code will be removed once fully transitioned to structured traces in Linux
            if (!this.linuxStructuredTracesEnabled)
            {
                switch (variants.Length)
                {
                    case 0:
                        this.variantEventWriter.VariantWriteLinuxUnstructured(ref this.descriptor, 0);
                        break;
                    case 1:
                        this.variantEventWriter.VariantWriteLinuxUnstructured(ref this.descriptor, 1, variants[0]);
                        break;
                    case 2:
                        this.variantEventWriter.VariantWriteLinuxUnstructured(ref this.descriptor, 2, variants[0], variants[1]);
                        break;
                    case 3:
                        this.variantEventWriter.VariantWriteLinuxUnstructured(ref this.descriptor, 3, variants[0], variants[1], variants[2]);
                        break;
                    case 4:
                        this.variantEventWriter.VariantWriteLinuxUnstructured(ref this.descriptor, 4, variants[0], variants[1], variants[2], variants[3]);
                        break;
                    case 5:
                        this.variantEventWriter.VariantWriteLinuxUnstructured(ref this.descriptor, 5, variants[0], variants[1], variants[2], variants[3], variants[4]);
                        break;
                    case 6:
                        this.variantEventWriter.VariantWriteLinuxUnstructured(ref this.descriptor, 6, variants[0], variants[1], variants[2], variants[3], variants[4], variants[5]);
                        break;
                    case 7:
                        this.variantEventWriter.VariantWriteLinuxUnstructured(
                            ref this.descriptor,
                            7,
                            variants[0],
                            variants[1],
                            variants[2],
                            variants[3],
                            variants[4],
                            variants[5],
                            variants[6]);
                        break;
                    case 8:
                        this.variantEventWriter.VariantWriteLinuxUnstructured(
                            ref this.descriptor,
                            8,
                            variants[0],
                            variants[1],
                            variants[2],
                            variants[3],
                            variants[4],
                            variants[5],
                            variants[6],
                            variants[7]);
                        break;
                    case 9:
                        this.variantEventWriter.VariantWriteLinuxUnstructured(
                            ref this.descriptor,
                            9,
                            variants[0],
                            variants[1],
                            variants[2],
                            variants[3],
                            variants[4],
                            variants[5],
                            variants[6],
                            variants[7],
                            variants[8]);
                        break;
                    case 10:
                        this.variantEventWriter.VariantWriteLinuxUnstructured(
                            ref this.descriptor,
                            10,
                            variants[0],
                            variants[1],
                            variants[2],
                            variants[3],
                            variants[4],
                            variants[5],
                            variants[6],
                            variants[7],
                            variants[8],
                            variants[9]);
                        break;
                    case 11:
                        this.variantEventWriter.VariantWriteLinuxUnstructured(
                            ref this.descriptor,
                            11,
                            variants[0],
                            variants[1],
                            variants[2],
                            variants[3],
                            variants[4],
                            variants[5],
                            variants[6],
                            variants[7],
                            variants[8],
                            variants[9],
                            variants[10]);
                        break;
                    case 12:
                        this.variantEventWriter.VariantWriteLinuxUnstructured(
                            ref this.descriptor,
                            12,
                            variants[0],
                            variants[1],
                            variants[2],
                            variants[3],
                            variants[4],
                            variants[5],
                            variants[6],
                            variants[7],
                            variants[8],
                            variants[9],
                            variants[10],
                            variants[11]);
                        break;
                    case 13:
                        this.variantEventWriter.VariantWriteLinuxUnstructured(
                            ref this.descriptor,
                            13,
                            variants[0],
                            variants[1],
                            variants[2],
                            variants[3],
                            variants[4],
                            variants[5],
                            variants[6],
                            variants[7],
                            variants[8],
                            variants[9],
                            variants[10],
                            variants[11],
                            variants[12]);
                        break;
                    case 14:
                        this.variantEventWriter.VariantWriteLinuxUnstructured(
                            ref this.descriptor,
                            14,
                            variants[0],
                            variants[1],
                            variants[2],
                            variants[3],
                            variants[4],
                            variants[5],
                            variants[6],
                            variants[7],
                            variants[8],
                            variants[9],
                            variants[10],
                            variants[11],
                            variants[12],
                            variants[13]);
                        break;
                    default:
                        throw new ArgumentOutOfRangeException("variants");
                }
                return;
            }
#endif
            switch (variants.Length)
            {
                case 0:
                    this.variantEventWriter.VariantWrite(ref this.descriptor, 0);
                    break;
                case 1:
                    this.variantEventWriter.VariantWrite(ref this.descriptor, 1, variants[0]);
                    break;
                case 2:
                    this.variantEventWriter.VariantWrite(ref this.descriptor, 2, variants[0], variants[1]);
                    break;
                case 3:
                    this.variantEventWriter.VariantWrite(ref this.descriptor, 3, variants[0], variants[1], variants[2]);
                    break;
                case 4:
                    this.variantEventWriter.VariantWrite(ref this.descriptor, 4, variants[0], variants[1], variants[2], variants[3]);
                    break;
                case 5:
                    this.variantEventWriter.VariantWrite(ref this.descriptor, 5, variants[0], variants[1], variants[2], variants[3], variants[4]);
                    break;
                case 6:
                    this.variantEventWriter.VariantWrite(ref this.descriptor, 6, variants[0], variants[1], variants[2], variants[3], variants[4], variants[5]);
                    break;
                case 7:
                    this.variantEventWriter.VariantWrite(
                        ref this.descriptor,
                        7,
                        variants[0],
                        variants[1],
                        variants[2],
                        variants[3],
                        variants[4],
                        variants[5],
                        variants[6]);
                    break;
                case 8:
                    this.variantEventWriter.VariantWrite(
                        ref this.descriptor,
                        8,
                        variants[0],
                        variants[1],
                        variants[2],
                        variants[3],
                        variants[4],
                        variants[5],
                        variants[6],
                        variants[7]);
                    break;
                case 9:
                    this.variantEventWriter.VariantWrite(
                        ref this.descriptor,
                        9,
                        variants[0],
                        variants[1],
                        variants[2],
                        variants[3],
                        variants[4],
                        variants[5],
                        variants[6],
                        variants[7],
                        variants[8]);
                    break;
                case 10:
                    this.variantEventWriter.VariantWrite(
                        ref this.descriptor,
                        10,
                        variants[0],
                        variants[1],
                        variants[2],
                        variants[3],
                        variants[4],
                        variants[5],
                        variants[6],
                        variants[7],
                        variants[8],
                        variants[9]);
                    break;
                case 11:
                    this.variantEventWriter.VariantWrite(
                        ref this.descriptor,
                        11,
                        variants[0],
                        variants[1],
                        variants[2],
                        variants[3],
                        variants[4],
                        variants[5],
                        variants[6],
                        variants[7],
                        variants[8],
                        variants[9],
                        variants[10]);
                    break;
                case 12:
                    this.variantEventWriter.VariantWrite(
                        ref this.descriptor,
                        12,
                        variants[0],
                        variants[1],
                        variants[2],
                        variants[3],
                        variants[4],
                        variants[5],
                        variants[6],
                        variants[7],
                        variants[8],
                        variants[9],
                        variants[10],
                        variants[11]);
                    break;
                case 13:
                    this.variantEventWriter.VariantWrite(
                        ref this.descriptor,
                        13,
                        variants[0],
                        variants[1],
                        variants[2],
                        variants[3],
                        variants[4],
                        variants[5],
                        variants[6],
                        variants[7],
                        variants[8],
                        variants[9],
                        variants[10],
                        variants[11],
                        variants[12]);
                    break;
                case 14:
                    this.variantEventWriter.VariantWrite(
                        ref this.descriptor,
                        14,
                        variants[0],
                        variants[1],
                        variants[2],
                        variants[3],
                        variants[4],
                        variants[5],
                        variants[6],
                        variants[7],
                        variants[8],
                        variants[9],
                        variants[10],
                        variants[11],
                        variants[12],
                        variants[13]);
                    break;
                default:
                    throw new ArgumentOutOfRangeException("variants");
            }
        }

        // If input is {3, 5, 8, 9} and {0, 2}, output -> {5, 9}, i.e values at index 0 and 2 are not included
        // in output.
        private static Variant[] ExceptPositions(Variant[] variants, int[] indexToRemove)
        {
            if (variants.Length == indexToRemove.Length)
            {
                return new Variant[0];
            }

            Variant[] inclusiveVariants = new Variant[variants.Length - indexToRemove.Length];
            for (int i = 0, j = 0; i < variants.Length; i++)
            {
                if (indexToRemove.Contains(i))
                {
                    continue;
                }

                inclusiveVariants[j++] = variants[i];
            }

            return inclusiveVariants;
        }

        public static int GetSetBitCount(int value)
        {
            int setBitCount = 0;

            //Loop the value while there are still bits
            while (value != 0)
            {
                //Remove the end bit
                value = value & (value - 1);
                setBitCount++;
            }

            return setBitCount;
        }

        private bool IsFileSinkEnabled()
        {
            return this.filterStates[(int)TraceSinkType.TextFile]
                   && TraceTextFileSink.IsEnabled;
        }

        private bool IsConsoleSinkEnabled()
        {
            return this.filterStates[(int)TraceSinkType.Console];
        }
    }
}
